package scalab.tasks.runners

import scalab.core.component.Task
import scala.tools.nsc.{MainGenericRunner, GenericRunnerCommand}
import scalab.utils.Reporter

/**
* Scala runner.
*/

class Scala(override val name: String, val args: Iterable[String]) extends JVMRunner{

	import java.io.File

	val classpath = optionalIn //must be forwarded to loaded task
	val bootClasspath = optionalIn //must be forwarded to loaded task

	def execute(destinationDir: String): Boolean = {

		val command = new GenericRunnerCommand(args.toList, error)
    val settings = command.settings

		val newClasspath = (classpath.sources map {d => d.canonicalPath}).mkString("", File.pathSeparator , "")
		if(!settings.classpath.isDefault){
			warning("Option '" + settings.classpath.name + "' with value '" + settings.classpath.value + 
				"' will be overriden to '" + newClasspath + "'!\nTo avoid this message appearance, " +
				"unset this option.")
		}
		settings.classpath.tryToSet(List("-cp", newClasspath))
		Scala.runner.run(command, this)
	
		
  }

	//this is a big hack because graph is modified during execution
	def setClasspath(paths: Iterable[String]){
		import scalab.core.component.{Universe, Target}
		import scalab.filters.selectors.Files
		val fs = Files(paths)
		Universe -> fs 
		val droper = fs.discard({d => false})
		fs >> (droper, classpath)
		val target = Target(".", droper) //no depende
		target.update
	}

	def instanceId: String = ""//take args to compute this
}

object Scala{

	def apply(runnerName: String): Scala = apply(runnerName, Nil)

	def apply(runnerName: String, options: Iterable[String]): Scala = new Scala(runnerName, options)

	def apply(options: Iterable[String]): Scala = apply("Scala", options)

	def apply(): Scala = apply(Nil)

	object runner{
		
		import java.lang.reflect.InvocationTargetException
		import java.net.URL
		import java.lang.{ClassNotFoundException, NoSuchMethodException}
		import java.io.File

		import scala.tools.nsc.{Properties, Global, ScriptRunner, InterpreterLoop, ObjectRunner}
		import scala.tools.nsc.util.ClassPath

		private def addClasspathExtras(classpath: String): String = {
 	    val scalaHome = Properties.scalaHome
 	
 	    val extraClassPath =
 	      if (scalaHome eq null)
 	        ""
 	      else {
 	        val libdir = new File(new File(scalaHome), "lib")
 	        if (!libdir.exists || libdir.isFile)
 	          return classpath
 	       
	        val filesInLib = libdir.listFiles
 	        val jarsInLib =
 	          filesInLib.filter(f =>
 	            f.isFile && f.getName.endsWith(".jar"))
 	 
 	        jarsInLib.mkString("", File.pathSeparator, "")
 	      }
 	   
 	    if (classpath == "")
 	      extraClassPath + File.pathSeparator + "."
 	    else
 	      classpath + File.pathSeparator + extraClassPath
 	  }


		def run(command: GenericRunnerCommand, reporter: Reporter): Boolean = {

    	val settings = command.settings
	
    	def sampleCompiler = new Global(settings)

    	if (!command.ok) {
      	reporter.error(command.usageMsg)
      	println(sampleCompiler.pluginOptionsHelp)
      	return false
    	}

    	settings.classpath.value =
      	addClasspathExtras(settings.classpath.value)
    
    	settings.defines.applyToCurrentJVM

    	if (settings.version.value) {
      	Console.println(
      	  "Scala code runner " +
      	  Properties.versionString + " -- " +
      	  Properties.copyrightString)
      	return true
    	}	


    	if (settings.help.value || settings.Xhelp.value || settings.Yhelp.value) {
      	if (command.settings.help.value) {
      	  println(command.usageMsg)
      	  println(sampleCompiler.pluginOptionsHelp)
      	}

      	if (settings.Xhelp.value) 
      	  println(command.xusageMsg)
	
      	if (settings.Yhelp.value) 
      	  println(command.yusageMsg)

      	return true
   	 }


    	if (settings.showPhases.value) {
      	println(sampleCompiler.phaseDescriptions)
      	return true
    	}

    	if (settings.showPlugins.value) {
      	println(sampleCompiler.pluginDescriptions)
      	return true
    	}

	    def fileToURL(f: File): Option[URL] =
  	    try { Some(f.toURL) }
  	    catch { case e => Console.println(e); None }
	
  	  def paths(str: String): List[URL] =
  	    for (
  	      file <- ClassPath.expandPath(str) map (new File(_)) if file.exists;
  	      val url = fileToURL(file); if !url.isEmpty
  	    ) yield url.get

  	  def jars(dirs: String): List[URL] =
  	    for (
  	      libdir <- ClassPath.expandPath(dirs) map (new File(_)) if libdir.isDirectory;
  	      jarfile <- libdir.listFiles if jarfile.isFile && jarfile.getName.endsWith(".jar");
  	      val url = fileToURL(jarfile); if !url.isEmpty
  	    ) yield url.get

  	  def specToURL(spec: String): Option[URL] =
  	    try { Some(new URL(spec)) }
  	    catch { case e => Console.println(e); None }

  	  def urls(specs: String): List[URL] =
  	    if (specs == null || specs.length == 0) Nil
  	    else for (
  	      spec <- specs.split(" ").toList;
  	      val url = specToURL(spec); if !url.isEmpty
  	    ) yield url.get

  	  val cp: List[URL] =
  	    paths(settings.bootclasspath.value) :::
  	    paths(settings.classpath.value) :::
  	    jars(settings.extdirs.value) :::
  	    urls(settings.Xcodebase.value)

  	  command.thingToRun match {
  	    case _ if settings.execute.value != "" =>
  	      val fullArgs =
					  command.thingToRun.toList ::: command.arguments
 			    ScriptRunner.runCommand(settings, 
						settings.execute.value,
						fullArgs)
					true

 	     case None =>
  	      (new InterpreterLoop).main(settings)
					true

  	   case Some(thingToRun) =>
  	   	val isObjectName =
  	    	settings.howtorun.value match {
  	      	case "object" => true
  	        case "script" => false
  	        case "guess" =>
  	        	ObjectRunner.classExists(cp, thingToRun)
  	      }

  	   	if (isObjectName) {

  	    	try {
  	      	ObjectRunner.run(cp, thingToRun, command.arguments)
						true
  	      } catch {
  	      	case e: ClassNotFoundException =>
  	        	reporter.error(e)
  	          false
  	        case e: NoSuchMethodException =>
  	          reporter.error(e)
  	          false
  	        case e: InvocationTargetException =>
  	          reporter.error(e.getCause.printStackTrace)
  	          false
  	      }

  	    } else {
  	    	try {
	        	ScriptRunner.runScript(settings, thingToRun, command.arguments)
						true
	        } catch {
	        	case e: SecurityException =>
	          	reporter.error(e)
	            false
	        }
	      }
	    }
		}
	}
}
